package com.zishuimuyu.utills;

import com.jcraft.jsch.*;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import java.io.*;
import java.lang.reflect.Field;
import java.util.*;

/**
 * 利用jsch.jar针对sftp的上传和下载
 * 需要jar包：
 * commons-lang3-3.2.1.jar
 * commons-logging.jar
 * jsch-0.1.53.jar
 * log4j-1.2.17.jar
 *
 * @author wangdy
 */
public class SftpUtil {

    private static final Log log = LogFactory.getLog(SftpUtil.class);

    /**
     * 利用JSch包实现SFTP下载文件
     * (这里是将serverPath下的指定扩展名的文件，全部下载到localPath下)
     *
     * @param ip            主机IP
     * @param userName      主机登陆用户名
     * @param passWord      主机登陆密码
     * @param port          主机ssh2登陆端口，如果取默认值，传<=0的值，如-1
     * @param localPath     下载的本地目录的路径
     * @param serverPath    服务器目录
     * @param fileExtension 文件扩展名,格式如：'.txt'
     * @param isDelete      是否删除该路径下扩展名为fileExetension的远程文件
     * @throws Exception
     */
    public static List<String> sshSftpDownload(String ip, String userName, String passWord, int port, String charset, String serverPath, String localPath, String fileExtension, boolean isDelete) throws Exception {
        Session session = null;
        Channel channel = null;
        ArrayList<String> fileNameList = new ArrayList<>();
        JSch jsch = new JSch();
        if (port <= 0) {
            // 连接服务器，采用默认端口
            session = jsch.getSession(userName, ip);
        } else {
            // 采用指定的端口连接服务器
            session = jsch.getSession(userName, ip, port);
        }
        // 如果服务器连接不上，则抛出异常
        if (session == null) {
            throw new Exception("session is null");
        }
        // 设置登陆主机的密码
        session.setPassword(passWord);
        // 设置第一次登陆的时候提示，可选值：(ask | yes | no)
        //session.setConfig("StrictHostKeyChecking", "no");
        Properties sshConfig = new Properties();
        sshConfig.put("StrictHostKeyChecking", "no");
        session.setConfig(sshConfig);
        // 设置登陆超时时间ms
        session.connect(1000);
        try {
            // 创建sftp通信通道
            channel = (Channel) session.openChannel("sftp");
            //设置连接超时时间
            channel.connect(1000);
            ChannelSftp sftp = (ChannelSftp) channel;
            log.info("Connected to " + ip + "." + "调整前sftp服务版本:" + sftp.getServerVersion());
            // 进入服务器指定的文件夹
            Class cl = ChannelSftp.class;
            Field f = cl.getDeclaredField("server_version");
            f.setAccessible(true);
            f.set(sftp, 2);
            sftp.setFilenameEncoding(charset);
            sftp.cd(serverPath);
            /**
             *  列出服务器指定的文件列表(可以加参数，指明要下载的文件类型)
             * 说明：如果fileExtension不为空，则下载指定扩展名的文件集；
             * 如果fileExtension为"",则下载该目录下所有类型的文件,如果是文件夹的话，会报错,如果您路径下有以.连接的文件夹，请注意，这是不可以的，程序会在过滤到该文件夹时中断
             */
            Vector<?> fileVectors = null;

            try {
                //ls -al | grep \"^-\"只显示文件---// ls -al | grep "^d"只显示目录包括.和..
                if (fileExtension != null && !"".equals(fileExtension)) {
                    fileVectors = sftp.ls("*" + fileExtension);
                } else {
                    fileVectors = sftp.ls("*.*");

                }
            } catch (Exception e) {
                log.info("您操作的是一个文件夹!");
            }
            if (fileVectors == null) {
                return fileNameList;
            }
            for (Object fileVector : fileVectors) {
                String[] fileInfos = fileVector.toString().replaceAll("\t", " ").split(" ");
                String fileName = fileInfos[fileInfos.length - 1];
                // 以下代码实现从服务器下载一个文件到 本地
                InputStream instream = sftp.get(fileName);
                OutputStream outstream = new FileOutputStream(new File(localPath + "/" + fileName));
                byte b[] = new byte[1024];
                int n;
                while ((n = instream.read(b)) != -1) {
                    outstream.write(b, 0, n);
                }
                outstream.flush();
                outstream.close();
                instream.close();
                fileNameList.add(fileName);
                log.info("文件[" + fileName + "]下载成功!---->>>>下载到目录[" + localPath + "]中.");
                //下载成功后，删除文件
                if (isDelete) {
                    deleteOneFile(sftp, serverPath, fileName);
                    log.info("文件[" + fileName + "]删除成功!");
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            session.disconnect();
            channel.disconnect();
        }
        return fileNameList;
    }

    /**
     * 利用JSch包实现SFTP下载文件
     * (这里是将serverPath下的指定名称的文件，下载到localPath下)
     *
     * @param ip         主机IP
     * @param userName   主机登陆用户名
     * @param passWord   主机登陆密码
     * @param port       主机ssh2登陆端口，如果取默认值，传<=0的值，如-1
     * @param localPath  下载的本地目录的路径
     * @param serverPath 服务器目录
     * @param fileName   文件名
     * @param isDelete   是否删除该路径下文件名为fileName的远程文件
     * @throws Exception
     */
    public static List<String> sftpDownload(String ip, String userName, String passWord, int port, String charset, String serverPath, String localPath, String fileName, boolean isDelete) throws Exception {
        Session session = null;
        Channel channel = null;
        ArrayList<String> fileNameList = new ArrayList<>();
        JSch jsch = new JSch();
        if (port <= 0) {
            // 连接服务器，采用默认端口
            session = jsch.getSession(userName, ip);
        } else {
            // 采用指定的端口连接服务器
            session = jsch.getSession(userName, ip, port);
        }
        // 如果服务器连接不上，则抛出异常
        if (session == null) {
            throw new Exception("session is null");
        }
        // 设置登陆主机的密码
        session.setPassword(passWord);
        // 设置第一次登陆的时候提示，可选值：(ask | yes | no)
        //session.setConfig("StrictHostKeyChecking", "no");
        Properties sshConfig = new Properties();
        sshConfig.put("StrictHostKeyChecking", "no");
        session.setConfig(sshConfig);
        // 设置登陆超时时间ms
        session.connect(1000);
        try {
            // 创建sftp通信通道
            channel = (Channel) session.openChannel("sftp");
            // 设置连接超时时间ms
            channel.connect(1000);
            ChannelSftp sftp = (ChannelSftp) channel;
            log.info("Connected to " + ip + "." + "调整前sftp服务版本:" + sftp.getServerVersion());
            // 进入服务器指定的文件夹
            Class cl = ChannelSftp.class;
            Field f = cl.getDeclaredField("server_version");
            f.setAccessible(true);
            f.set(sftp, 2);
            sftp.setFilenameEncoding(charset);
            sftp.cd(serverPath);
            InputStream instream = sftp.get(fileName);
            OutputStream outstream = new FileOutputStream(new File(localPath, fileName));
            byte b[] = new byte[1024];
            int n;
            while ((n = instream.read(b)) != -1) {
                outstream.write(b, 0, n);
            }
            outstream.flush();
            outstream.close();
            instream.close();
            fileNameList.add(fileName);
            log.info("文件[" + fileName + "]下载成功!---->>>>下载到目录[" + localPath + "]中.");
            //下载成功后，删除文件
            if (isDelete) {
                deleteOneFile(sftp, serverPath, fileName);
                log.info("文件[" + fileName + "]删除成功!");
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            session.disconnect();
            channel.disconnect();
        }
        return fileNameList;
    }

    /**
     * 利用JSch包实现SFTP上传文件
     * (这里是将serverPath下的指定扩展名的文件，全部下载到localPath下)
     *
     * @param ip            主机IP
     * @param userName      主机登陆用户名
     * @param passWord      主机登陆密码
     * @param port          主机ssh2登陆端口，如果取默认值，传<=0的值，如-1
     * @param localPath     本地目录
     * @param serverPath    服务器目录
     * @param fileExtension 上传文件的扩展名 格式如：'.txt'
     */
    public static List<String> sshSftpUpload(String ip, String userName, String passWord, int port, String charset, String localPath, String serverPath, String fileExtension) throws Exception {
        Session session = null;
        Channel channel = null;
        JSch jsch = new JSch();
        if (port <= 0) {
            // 连接服务器，采用默认端口
            session = jsch.getSession(userName, ip);
        } else {
            // 采用指定的端口连接服务器
            session = jsch.getSession(userName, ip, port);
        }
        // 如果服务器连接不上，则抛出异常
        if (session == null) {
            log.info("session is null，服务器连接失败");
            throw new Exception("session is null，服务器连接失败");
        }
        // 设置登陆主机的密码
        session.setPassword(passWord);
        // 设置第一次登陆的时候提示，可选值：(ask | yes | no)
        session.setConfig("StrictHostKeyChecking", "no");
        // 设置登陆超时时间ms
        session.connect(2000);
        ArrayList<String> fileNameList = new ArrayList<>();
        try {
            // 创建sftp通信通道
            channel = (Channel) session.openChannel("sftp");
            // 设置连接超时时间ms
            channel.connect(2000);
            ChannelSftp sftp = (ChannelSftp) channel;
            Class cl = ChannelSftp.class;
            Field f = cl.getDeclaredField("server_version");
            f.setAccessible(true);
            f.set(sftp, 2);
            sftp.setFilenameEncoding(charset);
            log.info("Connected Success, ip is [" + ip + "],SFTP服务版本" + sftp.getServerVersion() + "serverPath:" + serverPath);
            // 进入服务器指定的文件夹
            sftp.cd(serverPath);
            // 列出服务器指定的文件列表
            // 以下代码实现从本地上传一个文件到服务器，如果要实现下载，对换一下流就可以了
            String[] files = getLocalFileNameArray(localPath);//获取所有文件名数组

            for (int i = 0; i < files.length; i++) {
                String fileName = files[i];
                log.info("文件[" + localPath + "/" + fileName + "]开始上传!---->>>>上传到目录[" + serverPath + "]");

                if (fileExtension != null && !"".equals(fileExtension)) {
                    //如果扩展名不为空，则上传该路径下指定扩展名的文件
                    if (fileName.endsWith(fileExtension)) {
                        OutputStream outstream = sftp.put(fileName);
                        //要上传到服务器的文件，可以另外设个名字，也可以用本地的名称
                        //读取本地文件
                        InputStream instream = new FileInputStream(new File(localPath, fileName));
                        byte b[] = new byte[1024];
                        int n;
                        while ((n = instream.read(b)) != -1) {
                            outstream.write(b, 0, n);
                        }
                        outstream.flush();
                        outstream.close();
                        instream.close();
                        fileNameList.add(fileName);
                        log.info("文件[" + localPath + "/" + fileName + "]成功上传成功!---->>>>上传到目录[" + serverPath + "]中.");
                    } else {
                        log.info("警告：文件[" + fileName + "]不是指定类型[" + fileExtension + "]的文件");
                    }
                } else {//如果扩展名为空，则上传该路径下的所有文件
                    OutputStream outstream = sftp.put(fileName);//要上传到服务器的文件，可以另外设个名字，也可以用本地的名称
                    //本地文件
                    InputStream instream = new FileInputStream(new File(localPath, fileName));
                    byte b[] = new byte[1024];
                    int n;
                    while ((n = instream.read(b)) != -1) {
                        outstream.write(b, 0, n);
                    }
                    outstream.flush();
                    outstream.close();
                    instream.close();
                    fileNameList.add(fileName);
                    log.info("文件[" + fileName + "]上传成功!---->>>>上传到目录[" + serverPath + "]中.");
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            session.disconnect();
            channel.disconnect();
        }
        return fileNameList;
    }


    /**
     * 利用JSch包实现SFTP下载、上传文件（该方法暂不适用）
     *
     * @param ip         主机IP
     * @param userName   主机登陆用户名
     * @param passWord   主机登陆密码
     * @param port       主机ssh2登陆端口，如果取默认值(默认值22)，传-1
     * @param privateKey 密钥文件路径
     * @param passphrase 密钥的密码
     */
    public static ChannelSftp sftp(String ip, String userName, String passWord, int port, String charset, String privateKey, String passphrase) throws Exception {
        Session session = null;
        Channel channel = null;
        JSch jsch = new JSch();
        //设置密钥和密码
        if (privateKey != null && !"".equals(privateKey)) {
            if (passphrase != null && "".equals(passphrase)) {
                //设置带口令的密钥
                jsch.addIdentity(privateKey, passphrase);
            } else {
                //设置不带口令的密钥
                jsch.addIdentity(privateKey);
            }
        }
        if (port <= 0) {
            //连接服务器，采用默认端口
            session = jsch.getSession(userName, ip);
        } else {
            //采用指定的端口连接服务器
            session = jsch.getSession(userName, ip, port);
        }
        //如果服务器连接不上，则抛出异常
        if (session == null) {
            throw new Exception("session is null");
        }
        //设置登陆主机的密码
        session.setPassword(passWord);
        //设置第一次登陆的时候提示，可选值：(ask | yes | no)
        session.setConfig("StrictHostKeyChecking", "no");
        //设置登陆超时时间
        session.connect(30000);

        //创建sftp通信通道
        channel = (Channel) session.openChannel("sftp");
        channel.connect(1000);
        ChannelSftp sftp = (ChannelSftp) channel;
        Class cl = ChannelSftp.class;
        Field field = cl.getDeclaredField("server_version");
        field.setAccessible(true);
        field.set(sftp, 2);
        sftp.setFilenameEncoding(charset);
        return sftp;

    }

    /**
     * 读取指定路径下的所有文件
     *
     * @param localPath 指定路径
     * @return String[] 文件名数组
     */
    public static String[] getLocalFileNameArray(String localPath) {
        File diskFile = new File(localPath);
        String[] fileNameList = diskFile.list();
        if (fileNameList != null) {
            //按照文件名倒序排序
            Arrays.sort(fileNameList, Collections.reverseOrder());
        }
        return fileNameList;
    }


    /**
     * 删除指定目录的,指定扩展名的远程文件
     *
     * @param directory     要删除文件的目录
     * @param sftp          ChannelSftp实体
     * @param fileExtension 文件扩展名（删除远程文件，扩展名不能为空）
     */
    public void deleteAll(String directory, ChannelSftp sftp, String fileExtension) {
        try {
            sftp.cd(directory);
            Vector<?> v = null;
            if (fileExtension != null && "".equals(fileExtension)) {
                v = sftp.ls("*" + fileExtension);
            } else {
//				v=sftp.ls("");//此处有风险
                log.warn("FileExtension is not null! Please Check");
            }
            for (int i = 0; i < v.size(); i++) {
                String[] fileInfos = v.get(i).toString().replaceAll("\t", " ").split(" ");
                String fileName = fileInfos[fileInfos.length - 1];
                sftp.rm(fileName);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 删除单个文件
     *
     * @param directory  要删除文件所在目录
     * @param deleteFile 要删除的文件
     * @throws Exception
     */
    public static void deleteOneFile(ChannelSftp sftp, String directory, String deleteFile) throws Exception {
        sftp.cd(directory);
        sftp.rm(deleteFile);
    }

    /**
     * 删除单个文件
     *
     * @param serverPath        目录
     * @param fileNameExtension 文件名后缀
     * @throws Exception
     */
    public static List<String> listFile(ChannelSftp sftp, String serverPath, String fileNameExtension) throws Exception {
        List<String> filNameList = new ArrayList<>();
        sftp.cd(serverPath);
        Vector<?> fileVectors = null;
        try {
            //ls -al | grep \"^-\"只显示文件---// ls -al | grep "^d"只显示目录包括.和..
            if (fileNameExtension != null && !"".equals(fileNameExtension)) {
                fileVectors = sftp.ls("*" + fileNameExtension);
            } else {
                fileVectors = sftp.ls("*.*");
            }
        } catch (Exception e) {
            log.info("您操作的是一个文件夹!");
            e.printStackTrace();
        }
        if (fileVectors == null) {
            return filNameList;
        }
        for (Object fileVector : fileVectors) {
            String[] fileInfos = fileVector.toString().replaceAll("\t", " ").split(" ");
            String fileName = fileInfos[fileInfos.length - 1];
            filNameList.add(fileName);
        }
        return filNameList;
    }


}